// 
// OV-chip 2.0 project
// 
// Digital Security (DS) group at Radboud Universiteit Nijmegen
// 
// Copyright (C) 2008, 2009
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License in file COPYING in this or one of the
// parent directories for more details.
// 
// Created 27.8.08 by Hendrik
// 
// Host driver for protocol steps
// 
// $Id: Host_protocol.java,v 1.20 2009-05-20 11:02:38 tews Exp $

package ds.ov2.util;

import java.io.PrintWriter;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.CardException;


/** 
 * Host driver for steps in the OV-chip protocol layer. On the host
 * side the OV-chip protocol layer has a low and a high-level
 * interface. The low level interface is provided by the method {@link
 * #run_step run_step} of this class. For it, arguments and results
 * must be passed as arrays of {@link APDU_Serializable} (thus the
 * result containers must be allocated before running the step). 
 * <P>
 *
 * A more convenient high-level interface is generated by the IDL
 * compiler, see the generated *_stub classes. The stub code does also
 * create and maintain the necessary instances of this class. Normal
 * host driver code should therefore not come in contact with this
 * class. 
 * <P>
 *
 * This class is designed such that one instance must be created for
 * every protocol step. These instance can live for the whole duration
 * of the application. At creation time the parameters for progress
 * and debug output are fixed. 
 *
 * @author Hendrik Tews
 * @version $Revision: 1.20 $
 * @commitdate $Date: 2009-05-20 11:02:38 $ by $Author: tews $
 * @environment host
 * @CPP no cpp preprocessing needed
 */
public class Host_protocol {

    /**
     * 
     * The CLA byte for all the APDU's created here. Has value {@value}.
     */
    private static final byte CLA = 0;


    /**
     * 
     * The protocol identification number for this instance.
     */
    private final byte protocol_id;
    

    /**
     * 
     * The protocol step for this instance.
     */
    private final Protocol_step protocol_step;


    /**
     * 
     * The protocol step number.
     */
    private final byte step_id;


    /**
     * 
     * The output channel for progess and debug messages. Is null if
     * these messages are disabled.
     */
    private final PrintWriter out;


    /**
     * 
     * Whether to print apduscript lines.
     */
    private final boolean apduscript;


    /**
     * 
     * The name of the protocol step for the debugging output.
     */
    private final String name;


    /**
     * 
     * Create a new instance for running the protocol step {@code
     * step}. The protocol step of the new instance cannot be changed,
     * so it is necessary to create one instance for every protocol
     * step. The declared arguments and the declared results of the
     * step are not cached in this instance, so they might be updated
     * or changed after the creation of this instance (for instance
     * for changing their size in test frames). If the declared
     * arguments or results are changed, special care must be taken
     * that the declared arguments and results in the applet are
     * changed in the same way.
     * <P>
     *
     * Asserts that the protocol identifier is less than 0x70. (The
     * INS byte 0x70 is reserved for the manage channel command in the
     * global platform.)
     * <P>
     *
     * Throws also an assertion if the protocol identifier is less
     * than 0, which usually happens if the protocol is not linked in
     * some protocol array.
     * 
     * @param protocol the protocol to which {@code step} belongs
     * @param step the protocol step the new instance will run
     * @param out if non-null print progress and debug messages on it
     * @param apduscript if true print additional apduscript lines on
     * {@code out} 
     * @param name name of the protocol step for the debugging output
     * on {@code out}
     */
    public Host_protocol(Protocol protocol, Protocol_step step, 
                         PrintWriter out, boolean apduscript, String name) {
        this.protocol_step = step;
        this.out = out;
        this.apduscript = apduscript;
        this.name = name;
        protocol_id = protocol.protocol_id;
        
        // Has the protocol id been initialized as part of some
        // protocol array?
        assert protocol_id >= 0;

        // INS 0x70 is reserved
        assert protocol_id < 0x70;

        step_id = protocol_step.step_identifier;
    }


    /**
     * Print an APDU in human readable form to {@code out}. 
     * Suppress printing if {@code out} is null. Produce 
     * apduscript lines if {@code apduscript} is true.
     *
     * @param out channel to print to, might be null to supress printing
     * @param apdu APDU to print
     * @param msg string to insert at the start of the output
     * @param apduscript if true, additionally print apduscript lines
     * @param apdu_script_header string to print before the apduscript 
     *          line
     */
    public static void print_apdu_full(PrintWriter out, CommandAPDU apdu, 
                                       String msg, boolean apduscript,
                                       String apdu_script_header) 
    {
        if(out == null) return;

        out.format("%s CLA:%02X INS:%02X P1:%02X P2:%02X ",
                   msg,
                   apdu.getCLA(), apdu.getINS(), 
                   apdu.getP1(), apdu.getP2());

        String apdutool_line = "";
        if(apduscript)
            apdutool_line = String.format("// %s\n0x%02X 0x%02X 0x%02X 0x%02X",
                                          apdu_script_header, 
                                          apdu.getCLA(), apdu.getINS(), 
                                          apdu.getP1(), apdu.getP2());

        if(apdu.getNc() == 0 && apdu.getNe() == 0) {
            out.println("no send and no receive data");
            if(apduscript)
                out.format("%s 0x00 0x00;\n", apdutool_line);
            return;
        }
        
        if(apdu.getNc() > 0) {
            out.format("NC:%02X ", apdu.getNc());
            if(apduscript)
                apdutool_line = 
                    apdutool_line.concat(String.format(" 0x%02X",
                                                       apdu.getNc()));
            if(apdu.getNe() > 0) {
                out.format("NE:%02X ", apdu.getNe());
            }
            else {
                out.print("no NE ");
            }
            out.format("\n   data: ");
            byte[] data = apdu.getData();
            for(int i = 0; i < data.length; i++) {
                out.format("%02X", data[i]);
                if(apduscript)
                    apdutool_line = 
                        apdutool_line.concat(String.format(" 0x%02X", data[i]));
                if(i % 2 == 1 && i + 1 < data.length)
                    out.append('.');
                }
            out.format("\n");
        }
        else {
            out.format("no data NE:%02X\n", apdu.getNe());
            if(apduscript)
                // add only Nc to apdutool_line
                apdutool_line = apdutool_line.concat(" 0x00");
        }

        if(apduscript)
            out.format("%s 0x%02X;\n", apdutool_line, apdu.getNe());
    }
                   

    /**
     * Print an APDU in human readable form to {@code out}. 
     * Suppress printing if {@code out} is null. Special
     * version of {@link #print_apdu_full print_apdu_full} with
     * apduscript line printing disabled.
     *
     * @param out channel to print to, might be null to supress printing
     * @param apdu APDU to print
     * @param msg string to insert at the start of the output
     */
    public static void print_apdu(PrintWriter out, CommandAPDU apdu, 
                                  String msg) 
    {
        print_apdu_full(out, apdu, msg, false, "");
    }



    // Construct a standard APDU message. 
    // CLA = 0, INS = protocol_id, P1 = step_id, P2 = batch
    // If data_length > 0 copy a portion from the data array.
    // If response_length > 0 add an LE byte.
    // public byte[] make_apdu_message(byte p2, byte[] data, int data_index, 
    //                              int data_length, int response_length) {
    //  assert 0 <= data_length && data_length <= 255;
    //  assert 0 <= response_length && response_length <= 255;
    // 
    //  int message_len = 4;
    //  
    //  // XXX ???
    //  // CardChannel quirk: if both lc and le are zero CardChannel.send 
    //  // requires lc = 0 be present.
    //  if((data == null || data_length == 0) && response_length == 0)
    //      message_len += 1;
    //  else {
    //      if(data != null && data_length > 0)
    //          message_len += 1 + data_length;
    //      if(response_length != 0)
    //          message_len += 1;
    //  }
    // 
    //  byte[] message = new byte[message_len];
    //  message[0] = CLA;
    //  // INS 0x70 is reserved
    //  assert protocol_id < 0x70;
    //  message[1] = protocol_id;
    //  message[2] = step_id;
    //  message[3] = p2;
    //  if((data == null || data_length == 0) && response_length == 0)
    //      message[4] = 0;
    //  else {
    //      if(data != null && data_length > 0) {
    //          message[4] = (byte)data_length;
    //          System.arraycopy(data, data_index, message, 5, data_length);
    //      }
    //      if(response_length != 0)
    //          message[message_len -1] = (byte)response_length;
    //  }
    //  return message;
    // }


    /**
     * 
     * Send one APDU to the applet and expect {@code
     * check_response_length} bytes in the response APDU. Store the
     * response data in the {@code res} array at index {@code index}.
     * <P>
     *
     * The response APDU must contain precisely {@code
     * check_response_length} bytes, except for the case where {@code
     * check_response_length} equals -1, in which 255 or 256 response
     * bytes are accepted. If the applet returns a wrong number of
     * bytes an protocol error is indicated by throwing an {@link
     * Response_apdu.Card_response_error Card_response_error}
     * exception. <P>
     *
     * For a response status different from OK (ie, different from
     * 0x9000) an {@link Response_apdu.Card_response_error
     * Card_response_error} exception is thrown too. This could be
     * caused by an protocol error (eg., the applet expects a
     * different step or a different amount of argument or result
     * data) or any other problem in the applet (eg. a {@link
     * NullPointerException}).
     * 
     * @param card_channel communication channel to the applet
     * @param apdu the {@link CommandAPDU} to send
     * @param res byte array for the response data, must be non-null
     * even if no response is expected and {@code
     * check_response_length} is 0.
     * @param index starting index in {@code res} to copy the response
     * data 
     * @param check_response_length expected length of the data in the
     * response APDU; if the applet returns a different amount of data
     * an {@link Response_apdu.Card_response_error
     * Card_response_error} exception is thrown; as an exception the
     * value -1 means to accept either 255 or 256 response bytes
     * @return the number of bytes that have been copied into the
     * {@code res array}; if {@code check_response_length} is -1
     * either 255 or 256, otherwise the return value does always equal
     * {@code check_response_length}
     * @throws CardException for card communication errors
     * @throws Response_apdu.Card_response_error if a protocol error
     * occurs or the response status is different from 0x9000.
     */
    private int send_apdu_message(CardChannel card_channel,
                                 CommandAPDU apdu, byte[] res, int index, 
                                 int check_response_length) 
        throws CardException
    {
        if(out != null)
            print_apdu_full(out, apdu, "HP send apdu", apduscript, name);

        Response_apdu response = 
            new Response_apdu(card_channel.transmit(apdu));
        if(out != null)
            response.print(out, true);

        response.throw_if_not_ok(check_response_length);
        System.arraycopy(response.get_data(), 0, res, index, 
                         response.get_length());
        return response.get_length();
    }


    /**
     * 
     * The maximal number of data bytes in one command APDU. Has value
     * {@value}. 
     */
    private static final int max_send_bytes = 255;


    /**
     * 
     * The maximal value for the LE field, which indicates the
     * expected number of bytes in the response APDU. Has value
     * {@value}.
     */
    private static final int max_receive_bytes = 255;


    /**
     * 
     * Send a long APDU to the card. For the sake of this method a
     * long APDU can hava more than 255 bytes of data in the command
     * and the response APDU. The data to be send to the card is in
     * the {@code args} byte array.
     * <P>
     *
     * This method splits the data to be send into 255 byte APDU's and
     * calls {@link #send_apdu_message send_apdu_message} to send
     * everything to the applet. It issues sufficiently many response
     * APDU's to retrieve all results and assembles them in one byte
     * array. If the {@code out} argument of the {@link #Host_protocol
     * constructor} was non-null, appropriate progress and debugging
     * messages will be generated on that {@link PrintWriter}. 
     * <P>
     * 
     * If the response length is different from {@code res_length} a
     * protocol error is indicated by throwing an {@link
     * Response_apdu.Card_response_error Card_response_error}
     * exception. The same exception is thrown if the response status
     * of a (small) APDU is not OK (ie., different from 0x9000). The
     * latter could be caused by an protocol error (eg., the applet
     * expects a different step or a different amount of argument or
     * result data) or any other problem in the applet (eg. a {@link
     * NullPointerException}).
     * 
     * @param card_channel communication channel to the applet
     * @param args data to be sent to the card, can be between 0 and
     * 32640 bytes long
     * @param res_length the expected amount of respose data, must be
     * between 0 and 32640
     * @return the respose data in a byte array of length {@code
     * res_length} 
     * @throws CardException for card communication errors
     * @throws Response_apdu.Card_response_error if a protocol error
     * occurs or the response status of one (short) APDU is different
     * from 0x9000.
     */
    private byte[] send_long_apdu(CardChannel card_channel, 
                                  byte[] args, int res_length)
        throws CardException
    {
        // P2 contains the batch. The batch counts the APDU's needed to
        // send args or to receive res.
        int p2 = 0;
        int index = 0;
        CommandAPDU apdu;
        byte[] res = new byte[res_length];

        if(out != null)
            out.format("HP long apdu send %d bytes receive %d bytes\n",
                       args.length, res_length);

        // Send APDU's with arguments only, without receiving anything.
        while(index + max_send_bytes < args.length) {
            if(out != null)
                out.format("HP send bytes %d - %d\n", index, 
                           index + max_send_bytes);
            apdu = new CommandAPDU(CLA,          // CLA
                                   protocol_id,  // INS
                                   step_id,      // P1
                                   p2,           // P2
                                   args,         // data
                                   index,        // dataOffset
                                   max_send_bytes // dataLength
                                   );
            send_apdu_message(card_channel, apdu, res, 0, 0);
            p2 += 1;
            // First bit == 0 in the p2 byte indicates sending.
            // Therefore p2 must stay below 0x7f, otherwise the 
            // args array was too large.
            assert p2 <= 0x7f;
            index += max_send_bytes;
        }
        
        // Send now one APDU with the last argument bytes and receive 
        // already the first result bytes. 
        int send_len = args.length - index;

        // We always set a maximal respond length of max_receive_bytes, 
        // which is 255. The card however may send 256 bytes.
        // requested_response_len is what we send to the card as LE byte
        // to tell the card our expected response length. This will always
        // be between 0 .. max_receive_bytes (255).
        // check_response_len is equal to requested_response_len,
        // except for the case where sending more than 255 bytes would be
        // ok. In this case check_response_len is set to -1;
        // real_response_len is finally what we really get from the card.
        int requested_response_len, check_response_len, real_response_len;
        if(res_length > max_receive_bytes) {
            requested_response_len = max_receive_bytes;
            check_response_len = -1;
        }
        else {
            requested_response_len = res_length;
            check_response_len = res_length;
        }
        if(out != null)
            out.format("HP send bytes %d - %d receive bytes 0 - %d\n",
                       index, index + send_len, requested_response_len);
        apdu = new CommandAPDU(CLA,              // CLA
                               protocol_id,      // INS
                               step_id,          // P1
                               p2,               // P2
                               args,             // data
                               index,            // dataOffset
                               send_len,         // dataLength
                               requested_response_len // NE
                               );
        real_response_len = send_apdu_message(card_channel,
                                              apdu, res, 0, 
                                              check_response_len);
        index = real_response_len;
        // First bit == 1 in the P2 byte indicates receiving.
        p2 = 0x80;
        p2 += 1;

        // Send now a couple of empty APDU's to receive the remainder.
        while(index < res_length) {
            requested_response_len = res_length - index;
            if(requested_response_len > max_receive_bytes) {
                requested_response_len = max_receive_bytes;
                check_response_len = -1;
            }
            else {
                check_response_len = requested_response_len;
            }
            if(out != null)
                out.format("HP receive bytes %d - %d\n",
                           index, index + requested_response_len);
            apdu = new CommandAPDU(CLA,          // CLA
                                   protocol_id,  // INS
                                   step_id,      // P1
                                   p2,           // P2
                                   requested_response_len // NE
                                   );
            real_response_len = send_apdu_message(card_channel,
                                                  apdu, res, index, 
                                                  check_response_len);
            p2 += 1;
            // First bit == 1 in the P2 byte indicates receiving.
            // Therefore p2 must stay below 0xff.
            assert p2 <= 0xff;
            index += real_response_len;
        }

        return res;
    }


    /**
     * 
     * Run the protocol step of this instance. Check the <A
     * HREF="../util/APDU_Serializable.html#apdu_compatibility">compatibility</A>
     * of the actual arguments and results against the declared
     * arguments and results of the {@link Protocol_step} argument of
     * the {@link #Host_protocol constructor}. Serialize the
     * arguments, split them into 255 byte APDU's and send everything
     * to the applet. Issue sufficiently many response APDU's to
     * retrieve all results and deserialize them into the provided
     * result containers. If the {@code out} argument of the {@link
     * #Host_protocol constructor} was non-null, appropriate progress
     * and debugging messages will be generated on that {@link
     * PrintWriter}. <P>
     *
     * This method does the compatibility checks and
     * serialization/deserialization. The remainder is done by {@link
     * #send_long_apdu send_long_apdu}.
     * <P>
     *
     * If the applet response does not meet the expectations a
     * protocol error is indicated by throwing an {@link
     * Response_apdu.Card_response_error Card_response_error}
     * exception. The same exception is thrown if the response status
     * of one APDU is not OK (ie., different from 0x9000). The latter
     * could be caused by an protocol error (eg., the applet expects a
     * different step or a different amount of argument or result
     * data) or any other problem in the applet (eg. a {@link
     * NullPointerException}).
     * 
     * @param card_channel communication channel to the applet
     * @param arguments the arguments of this step
     * @param results APDU_Serializable containers for the results
     * @throws IllegalArgumentException if the compatibility check fails
     * @throws NullPointerException if one of the arrays contains a null
     *              reference
     * @throws CardException for card communication errors
     * @throws Response_apdu.Card_response_error if a protocol error
     * occurs or the response status of one APDU is different from
     * 0x9000.
     */
    public void run_step(CardChannel card_channel,
                         APDU_Serializable[] arguments, 
                         APDU_Serializable[] results)
        throws CardException,
               IllegalArgumentException, NullPointerException
    {
        if(out != null)
            out.format("HP Start %s with %d arguments %d results\n",
                       name,
                       arguments == null ? 0 : arguments.length,
                       results == null ? 0 : results.length);

        // Check that supplied arguments and results are of the right
        // number and type.
        Convert_serializable.check_compatibility(arguments, 
                                                 protocol_step.arguments);
        Convert_serializable.check_compatibility(results,
                                                 protocol_step.results);

        byte[] serialized_args = Convert_serializable.array_to_bytes(arguments);

        int res_size = Misc.length_of_serializable_array(results);

        byte[] serialized_res = 
            send_long_apdu(card_channel, serialized_args, res_size);

        int res = 
            Convert_serializable.array_from_bytes(serialized_res, 0, results);
        assert res == res_size;

        return;
    }
}
